<?php
declare (strict_types = 1);

namespace tool;

/**
 * 资料来源：支付宝
 * 签名：所有交互的数据都需要签名，除了 sign(签名)，和 sign_type(签名方式：md5,rsa)
 * md5签名
 * 1.私钥：只有你和服务器知道，用于加密数据，由英文字母和数据组成，共32位
 * 2.请求时签名：$sign=md5((待签名字符串.key+md5私钥));
 * 3.返回时验证：$_POST['sign']==md5((待验证字符串,md5私钥))
 * md5签名  key:　ello123456789000
 * 空值""除外，所有参数按key排序并拼接key＝md5私钥，如下string
 * md5(money=5.60&title=app&ts=1592002222&key=ello123456789000)转大写
 * 注意：如果是get请求，参数要urlencode传输
 * rsa签名
 * 1.私钥与公钥：openssl生成
 * 2.请求时签名：$sign = rsaSign(待签名字符串,私钥);
 * 3.返回时验证：$_POST['sign'] == rsaVerify(待验证字符串,公钥);
 */

/**
 * 演示md5签名过程
 */


class Md5Sign
{
    public $_sign_type = 'md5';
    public $_md5_key = '';

    /**
     * 除去数组中的空值和签名参数
     * @param $para 签名参数组
     * return 去掉空值与签名参数后的新签名参数组
     */
    public function paraFilter($para)
    {
        $para_filter = array();
        foreach ($para as $key => $val){
            if ($key == "sign" || $key == "sign_type" || $val == "") continue;
            else    $para_filter[$key] = $para[$key];
        }
        return $para_filter;
    }

    /**
     * 对数组排序
     * @param $para 排序前的数组
     * return 排序后的数组
     */
    function argSort($para)
    {
        ksort($para);
        reset($para);
        return $para;
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param $para 需要拼接的数组
     * return 拼接完成以后的字符串
     */
    public function createLinkstring($para)
    {
        $arg = "";
        foreach ($para as $key => $val){
            $arg .= $key . "=" . $val . "&";
        }
        ////去掉最后一个&字符
        //$arg = substr($arg, 0, -1);
        //如果存在转义字符，那么去掉转义
        if (get_magic_quotes_gpc()) {
            $arg = stripslashes($arg);
        }
        return $arg;
    }

    /**
     * 生成md5签名字符串
     * @param $prestr 需要签名的字符串
     * @param $key 私钥
     * return 签名结果
     */
    public function md5Sign($prestr, $key)
    {
        $prestr = $prestr . 'key=' .$key;
        return strtoupper(md5($prestr));
    }

    /**
     * 对参数进行过滤，然后排序
     * @param array $para_temp
     */
    function filterPara($para_temp)
    {
        $para_filter = $this->paraFilter($para_temp);//除去待签名参数数组中的空值和签名参数
        return $this->argSort($para_filter);         //对待签名参数数组排序
    }

    /**
     * 生成签名结果
     * @param $para_sort 已排序要签名的数组
     * @return string 签名结果字符串
     */
    function buildRequestMysign($para_sort)
    {
        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->createLinkstring($para_sort);
        $mysign = $this->md5Sign($prestr, $this->_md5_key);
        return $mysign;
    }

    /**
     * 生成要发送给支付宝的参数数组
     * @param $para_temp 请求前的参数数组
     * @return 要请求的参数数组
     */
    public function buildRequestPara($para_temp)
    {
        $para_sort = $this->filterPara($para_temp);                //对待签名参数进行过滤
        $para_sort['sign'] = $this->buildRequestMysign($para_sort);//生成签名结果，并与签名方式加入请求提交参数组中
        $para_sort['sign_type'] = strtoupper(trim($this->_sign_type));
        return $para_sort;
    }
}


//1.准备要提交的字符串
//$para_token = array(
//    'title'=> 'app',
//    'money'=>'5.60',
//    'ts' => '1592002222'
//);

//$md5 = new Md5Sign('md5');
//
//// 2.准备md5私钥
//$md5->_md5_key = 'd41d8cd98f00b204e9800998ecf8427e';
//// 3.生成要提交的数据
//$a = $md5->buildRequestPara($para_token);
//
////打印结果：Array ( [money] => 5.6 [title] => app [sign] => 9ae9554fac509eea96e6e8efba6846d5 [sign_type] => MD5 )
//
////4.假设现在服务器返回以下数据，如何验证此数据是由服务发送的
//$return = array(
//    'title'=> 'app',
//    'money'=>'5.60',
//    'sign'=>'9ae9554fac509eea96e6e8efba6846d5',
//    'sign_type'=>'MD5'
//);
////5.按照加密流程：过滤数数组->把数组转为字符串->生成签名，检查是否生成同样的签名
//$md5_vertiry = new Md5Sign();
//$md5_vertiry->_md5_key =  'd41d8cd98f00b204e9800998ecf8427e';
//$return = $md5_vertiry->filterPara($return);
//echo $md5_vertiry->buildRequestMysign($return)."<br/>";
////如果生成的签名相等，即验证成功
//
//if($a['sign']==$md5_vertiry->buildRequestMysign($return)){
//    echo "验证成功";
//}else{
//    echo "验证失败";
//}